﻿using System;
using System.Net;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Security;
using System.Security.Cryptography.X509Certificates;
using System.ServiceProcess;
using PS.WSSDeploy.Contract;
using System.IdentityModel.Selectors;
using System.IdentityModel.Tokens;
using System.Diagnostics;

namespace PS.WSSDeploy.Service
{
    public partial class WindowsService : ServiceBase
    {
        private ServiceHost host = null;
        private string urlService = "";
        private EventLog EventLog1 = null;

        public WindowsService()
        {
            InitializeComponent();
            InitEventLog();
        }

        private void InitEventLog()
        {
            EventLog1 = new EventLog();

            if (!EventLog.SourceExists("WSSDeployService"))
                EventLog.CreateEventSource(
                   "WSSDeployService", "Application");

            EventLog1.Source = "WSSDeployService"; 
        }

        protected override void OnStart(string[] args)
        {
            try
            {
                bool hasConfiguredAddress = string.IsNullOrEmpty(Config.ServiceHost);
                string socketAddress;
                if (hasConfiguredAddress)
                {
                    IPHostEntry ips = Dns.GetHostEntry(Dns.GetHostName());
                    IPAddress _ipAddress = ips.AddressList[0];
                    socketAddress = _ipAddress.ToString();
                } else 
                    socketAddress = Config.ServiceHost;

                urlService = "net.tcp://" + socketAddress
                    + ":" + Config.ServicePort + "/MyService";
                
                host = new ServiceHost(typeof(WCFService));

                var binding = new NetTcpBinding();

                if (string.IsNullOrEmpty(Config.ServiceCertificateSerialNumber))
                {
                    binding.TransactionFlow = false;
                    binding.Security.Transport.ProtectionLevel = System.Net.Security.ProtectionLevel.EncryptAndSign;
                    binding.Security.Transport.ClientCredentialType = TcpClientCredentialType.Windows;
                    binding.Security.Mode = SecurityMode.None;                  
                }
                else
                {
                    host.Credentials.ClientCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.Custom;
                    host.Credentials.ClientCertificate.Authentication.CustomCertificateValidator =
                        new CustomX509CertificateValidator(EventLog1);
                    binding.Security.Mode = SecurityMode.Transport;
                    binding.Security.Transport.ClientCredentialType = TcpClientCredentialType.Certificate;

                    host.Credentials.ServiceCertificate.SetCertificate(
                        StoreLocation.LocalMachine,
                        StoreName.My,
                        X509FindType.FindBySerialNumber,
                        Config.ServiceCertificateSerialNumber);
                }

                binding.MaxReceivedMessageSize = Config.MaxReceivedMessageSize;
                binding.ReaderQuotas.MaxArrayLength = Config.ReaderQuotas_MaxArrayLength;

                host.AddServiceEndpoint(typeof(IWSSDeployContract), binding, urlService);

                var metadataBehavior = host.Description.Behaviors.Find<ServiceMetadataBehavior>();
                if (metadataBehavior == null)
                {
                    metadataBehavior = new ServiceMetadataBehavior();
                    metadataBehavior.HttpGetUrl = new Uri("http://" + socketAddress 
                        + ":" + Config.MetadataPort + "/MyService");
                    metadataBehavior.HttpGetEnabled = true;
                    metadataBehavior.ToString();
                    host.Description.Behaviors.Add(metadataBehavior);
                }
               
                var debugBehavior = host.Description.Behaviors.Find<ServiceDebugBehavior>();
                if (null == debugBehavior)
                {
                    debugBehavior = new ServiceDebugBehavior();
                    debugBehavior.IncludeExceptionDetailInFaults = true;
                    host.Description.Behaviors.Add(debugBehavior);
                }
                else
                {
                    debugBehavior =
                        (ServiceDebugBehavior) host.Description.Behaviors[typeof (ServiceDebugBehavior)];
                    debugBehavior.IncludeExceptionDetailInFaults = true;
                }

                host.Open();
            }
            catch (Exception ex)
            {
                EventLog1.WriteEntry(
                    string.Format("Service cannot be started on {1}\nError: {0}", ex.Message, urlService), EventLogEntryType.Error);
                throw;
            }
        }

        protected override void OnStop()
        {
            if (host != null)
            {
                host.Close();
                host = null;
            }
        }

        public class CustomX509CertificateValidator : X509CertificateValidator
        {
            private EventLog EventLog1 = null;

            public CustomX509CertificateValidator(EventLog el)
            {
                EventLog1 = el;
            }

            public override void Validate(X509Certificate2  certificate)
            {
                if (certificate == null)
                {
                    throw new ArgumentNullException("certificate");
                }

                if (certificate.SubjectName.Name != Config.ClientCertificateSubjectName) 
                {
                    throw new SecurityTokenException("Certificate validation failed");
                }
            }
        }
    }
}
